/*
 * Systemprogrammierung
 * Multiplayer-Quiz
 *
 * Server
 *
 * catalog.c: Implementierung der Fragekatalog-Behandlung und Loader-Steuerung
 */

#include "common/sockets.h"
#include "common/server_loader_protocol.h"
#include "common/question.h"
#include "catalog.h"
#include "clientthread.h"

/* ** ** ** Globable Variablen für Shared Memory und Frageverwaltung ** ** */

Question *shmem;	/**< Pointer auf Shared Memory */

size_t shmem_size;	/**< Größe des gesamten Shared Memory */

pid_t pid;	/**< Prozess ID von Loader */

/** Pipes für Kommunikation mit Loader */
int pipe_loader_server_std[2];
int pipe_server_loader[2];

/** String Arrays für Verarbeitung der Katalogliste */
char * ausgabe = NULL;
char * ausgabe_realloc;
char ** filelist = NULL;
char ** filelist_realloc;

/** Streams für Kommunikation über Pipes */
FILE *stream_w;
FILE *stream_r;
FILE *errstream;

bool debug_global = false;	/**< Debugmodus an Loader weiterreichen */

int num_of_questions = 0;	/**< Anzahl der geladenen Fragen */

/**	\brief	Loader 'Load' Befehl auslösen
 *  Löst den Load-Befehl des Loaders aus
 *  \param filename Beinhaltet die zu ladende Datei
 *  \param errorstring String für die Fehlermeldung wenn einer auftritt */

int loader_load(char* filename,	char* errorstring) {

	// Rueckgabestring von Loader
	char errormsg[50] = { 0 };

	// Befehl an Loader senden
	fputs("LOAD ", stream_w);
	// Filename = ausgewählter Katalog
	fputs(filename, stream_w);
	// Loader-Zeichen für Katalogende
	fputs("\n", stream_w);
	// Stream schreiben
	fflush(stream_w);

	// Antwort von Loader empfangen
	if (fgets(errormsg, 50, stream_r) != NULL) {

		//bei Fehler
		if (strncmp(errormsg, LOAD_ERROR_PREFIX, strlen(LOAD_ERROR_PREFIX))
				== 0) {

			// Lösche Error-Prefix
			memmove(errormsg, errormsg + strlen(LOAD_ERROR_PREFIX), 50);

			// Unterscheide Error-Typen und schreibe passende Nachricht in Error-String von Client-Thread
			if (strncmp(errormsg, LOAD_ERROR_CANNOT_OPEN, strlen(
					LOAD_ERROR_CANNOT_OPEN)) == 0) {

				strcpy(errorstring, "Katalog konnte nicht geöffnet werden.");
			}

			if (strncmp(errormsg, LOAD_ERROR_CANNOT_READ, strlen(
					LOAD_ERROR_CANNOT_READ)) == 0) {

				strcpy(errorstring, "Katalog konnte nicht gelesen werden.");
			}

			if (strncmp(errormsg, LOAD_ERROR_INVALID,
					strlen(LOAD_ERROR_INVALID)) == 0) {

				strcpy(errorstring, "Katalogformat ist ungültig.");
			}

			if (strncmp(errormsg, LOAD_ERROR_SHMEM, strlen(LOAD_ERROR_SHMEM))
					== 0) {

				strcpy(errorstring, "Interner Server-Fehler: shmem!");
			}

			if (strncmp(errormsg, LOAD_ERROR_OOM, strlen(LOAD_ERROR_OOM)) == 0) {

				strcpy(errorstring, "Interner Server-Fehler: oom!");
			}
		}

		//bei Erfolg: Anzahl der Fragen zurückgeben
		if (strncmp(errormsg, LOAD_SUCCESS_PREFIX, sizeof(LOAD_SUCCESS_PREFIX)
				- 1) == 0) {

			// Lösche Prefix
			memmove(errormsg, errormsg + sizeof(LOAD_SUCCESS_PREFIX) - 1, 50);

			// Bei Erfolg: Gebe Anzahl der geladenen Fragen als INTEGER an Client-Thread zurück

			num_of_questions = atoi(errormsg);

			return num_of_questions;
		}
	}

	// Bei Fehlerfällen
	return -1;
}

/* ** ** ** Hole Liste der Fragekataloge von Loader ** ** */

/**	\brief	Loader 'Browse' Befehl auslösen
 *  Löst den Browse-Befehl des Loaders aus */

char ** loader_browse() {

	// Zähler für einzelnen Char
	unsigned int count = 0;

	// Zähler für gelesenen Char oder Katalog
	int ch;

	// Pointer auf einzelnen Katalog
	char * pch;

	char oldchar = '\0';

	debugPrint("Versuche Browse auszulösen ...\n");

	fputs("BROWSE\n", stream_w);

	fflush(stream_w);

	debugPrint("Browse geschrieben!\n");

	debugPrint("Lese Dateinamen ein ...\n");

	while ((ch = getc( stream_r )) != EOF) { //Für jedes Zeichen dass vom Loader auf der Konsole ausgegeben wird (gepiped hier in den Server)

		ausgabe_realloc = (char*) realloc(ausgabe, count + 1); //einen neues char allocieren

		if (ausgabe_realloc != NULL) {

			ausgabe = ausgabe_realloc; //&dass übergebene Zeichen dort abspeichern

			ausgabe[count] = (char) ch;

			if (ausgabe[0] == '\n') {
				return NULL;
			}

		} else {

			free(ausgabe); //Falls dass allocieren fehlschlägt

			debugPrint("Fehler beim reallocieren!");

			exit(255);
		}

		count++; //Count z�hlt die bereits �bergebenen Zeichen

		if (oldchar == '\n' && ch == '\n') {

			break;
		}

		oldchar = ch;
	}

	memset(ausgabe + count, 0, 1); // Füge Ende-Markierung ein

	count = 0;

	pch = strtok(ausgabe, "\n"); // Gesamtstring bei Newline teilen in seperate Strings

	//Da wir nun ein riesiges Array vom Loader bekommen haben müssen wir es noch splitten und zwar an jedem Zeilenumbruch
	while (pch != NULL) {

		filelist_realloc
				= (char**) realloc(filelist, count * sizeof(char*) + 1); //Wir allocieren für jede Zeile einen doppelten Charpointer

		if (filelist_realloc != NULL) {

			filelist = filelist_realloc;

			filelist[count] = (char*) malloc(strlen(pch) + 1); //Außerdem allocieren wir für jede Zeile den nötigen speicher und richten den entsprechenden iterator (count) auf dass neu allocierte array

			strcpy(filelist[count], pch); //Der String aus der großen Zeichenkette wird in den eben allocierten Bereich kopiert.
		}

		debugPrint("%s", filelist[count]);

		pch = strtok(NULL, "\n");

		count++;
	}

	// Ende Markierung
	filelist[count + 1] = (char*) malloc(1);
	filelist[count + 1] = '\0';

	fflush(stdout);

	debugPrint("Lesen abgeschlossen\n");

	return (filelist);
}

/**	\brief	Startet den Loader
 *  Startet den Loader
 *  \param kat Beinhaltet das Verzeichnis mit den Katalogen
 *  \param debug Entscheidet über die Startparameter des Loaders */

int start_loader(char* kat, bool debug) {

	// Erzeuge Pipe für Loader -> Server
	if (pipe(pipe_loader_server_std) != 0) {
		perror("pipe:");
		exit(255);
	}

	// Erzeuge Pipe für Server->Loader
	if (pipe(pipe_server_loader) != 0) {
		perror("pipe:");
		exit(255);
	}

	// Öffne Streams auf Pipes
	if ((stream_w = fdopen(pipe_server_loader[1], "w")) == NULL) {
		perror("stream pipe:");
		exit(255);
	}
	if ((stream_r = fdopen(pipe_loader_server_std[0], "r")) == NULL) {
		perror("stream pipe:");
		exit(255);
	}

	// Prozess abspalten
	pid = fork();

	// Fehler abfangen
	if (pid < 0) {
		perror("fork:");
		exit(255);

	} else

	// Kindprozess
	if (pid == 0) {

		dup2(pipe_server_loader[0], STDIN_FILENO); //Lesendes Ende auf Standart eingabe
		dup2(pipe_loader_server_std[1], STDOUT_FILENO); //Ausgabe it pipe umleiten

		/* überflüssige Pipe-discreptoren schließen */
		close(pipe_server_loader[0]);
		close(pipe_server_loader[1]);
		close(pipe_loader_server_std[0]);
		close(pipe_loader_server_std[1]);

		/*Neues Programm in die Arbeitsumgebung laden*/
		if (debug == true) {

			debug_global = debug;

			if (execlp("./loader", "./loader", kat, "-d", NULL) == -1) {
				perror("exec loader:");
			}

		} else {

			if (execlp("./loader", "./loader", kat, NULL) == -1) {
				perror("exec loader:");
			}
		}

	} else

	//Im Elternprozess
	if (pid > 0) {

		close(pipe_server_loader[0]);
		close(pipe_loader_server_std[1]);

		char** catalogs = loader_browse();

		if (catalogs == NULL) {
			return (-1);
		} else {
			set_catalogs(catalogs);
		}
	}

	// Stream leeren
	fflush(stdout);

	return (0);
}

/**	\brief	Beendet den Loader
 *  Beendet den Loader und gibt reservierte Ressourcen wieder frei */

int end_loader() {
	//Pfad zum Shared Momory ermitteln
	/*char* folder = "/dev/shm";
	char* path;
	path = (char*)malloc(sizeof(SHMEM_NAME)+sizeof(folder));
	memcpy(path,folder,sizeof(folder));
	strcat(path,SHMEM_NAME);*/
	// Gebe Speicher für Katalogliste frei
	free(ausgabe_realloc);
	free(filelist_realloc);

	// Schliesse Streams
	fclose(stream_w);
	fclose(stream_r);

	// Shared Memory Adressraum freigeben
	if (munmap(shmem, shmem_size) == -1) {
		perror("SharedMem UnMap:");
	}
	/**	\brief	Beendet den Loader
	 *
	 *  Beendet den Loader und gibt reservierte Ressourcen wieder frei */

	// Shared Memory freigeben
	if (shm_unlink(SHMEM_NAME) == -1) {
		perror("SharedMem UnLink:");
	}
	//Shared Memory in Verzeichnis entfernen
	/*if( remove(path) != 0 ) {
		perror("SharedMem remove:");
	}*/

	return 0;
}

/* ** ** ** Öffne und Mappe Shared Memory ** ** */


/**	\brief	Öffnet den Shared Memory
 *  Öffnet den Shared Memory lesend für den Server
 *  \param number */

int open_shared_mem(int number) {

	int shmHandle;

	shmem_size = number * sizeof(Question);

	shmHandle = shm_open(SHMEM_NAME, O_RDONLY, 0);

	if (shmHandle == -1) {

		switch (errno) {

		case EEXIST:
			errorPrint("Shared Memory wurde noch nicht angelegt.");
			break;

		default:
			errorPrint("Fehler beim öffnen des Shared Memorys: %s", strerror(
					errno));
			break;
		}

		return -1;
	}

	shmem = mmap(NULL, shmem_size, PROT_READ, MAP_SHARED, shmHandle, 0);

	if (shmem == MAP_FAILED) {

		errorPrint("Kann Shared Memory nicht in Adressraum einbinden: %s",
				strerror(errno));
		close(shmHandle);

		shm_unlink(SHMEM_NAME);

		return -1;
	}

	return 0;
}

/**	\brief	Liest eine Frage aus dem shared Memory aus
 *  Liest eine Frage an der angebenen Poisition aus dem Shared Memory aus.
 *  \param position Position der Frage im Shared Memory */

Question* get_question(int* position) {

	if (*position == num_of_questions) {
		return NULL;
	}

	Question* tmp_ptr = shmem + *position;

	return tmp_ptr;
}

